vite+vue3 知识体系
知识点
- Teleporting
- 双向数据绑定
- vite 配置
- 插槽的变化
- https://loading.io/ loading图标
(一) 创建项目
npm init vite@latest
(二) mvvm 框架介绍
- m model 后台数据
- vm vue
- v view 视图, 展示出来的页面
(三) 添加element-plus
- 安装依赖
npm install element-plus --save
(四) 变量和响应式变量
- 变量可以直接放入 template 内显示在页面上, 但不具备响应式效果
<template>
{{ msg }}
</template>
<script setup>
import HelloWorld from "./components/HelloWorld.vue";
let msg = "hahaha";
setTimeout(() => {
msg = "2222";
}, 2000);
</script>
- 响应式变量
- 使用 ref 创建响应式变量
- 使用 xxx.value 或者 String(xxx)获取响应式变量的值
<template>
{{ msg }}
</template>
<script setup>
import { ref } from "vue";
let msg = ref("hahaha");
setTimeout(() => {
msg.value = "heieheiehieie";
}, 2000);
</script>
(六) 响应式对象
<template>
<span>{{ user.username }}</span>
<hr>
<button @click="setUsername">修改</button>
</template>
<script setup>
import { ref, reactive } from "vue";
const user = reactive({
username: "老胡",
age: 100,
});
const setUsername = () => {
user.username = "胡老";
};
</script>
## 事件
1. .native 修饰符不再需要, 可以直接使用原生事件
2. $event
```vue
<template>
<span>{{ user.username }}</span>
<hr />
<button @click="setUsername('李四', $event)">修改</button>
</template>
<script setup>
import { ref, reactive } from "vue";
const user = reactive({
username: "老胡",
age: 100,
});
const setUsername = (newName, event) => {
user.username = newName;
console.log(event);
};
</script>
(七) $refs
vue3.2 如何获取子组件实例??
(八) 计算属性
- 使用普通方法,每使用一次就调用一次方法
- 使用 computed 可以缓存结果
- 对计算属性进行修改
1.普通方法
方法执行了三次
<template>
<span>{{ str }}</span> <br />
<span>{{ reverseStr(str) }}</span>
<span>{{ reverseStr(str) }}</span>
<span>{{ reverseStr(str) }}</span>
</template>
<script setup>
import { ref } from "vue";
const str = ref("我怕太太");
let count = 0;
const reverseStr = (str) => {
console.log(++count);
return str.split("").reverse().join("");
};
</script>
2.计算属性
方法只执行一次
<template>
<span>{{ str }}</span> <br />
<span>{{ str2 }}</span>
<span>{{ str2 }}</span>
<span>{{ str2 }}</span>
</template>
<script setup>
import { ref, computed } from "vue";
const str = ref("我怕太太");
let count = 0;
let str2 = computed(() => {
console.log(++count);
return str.value.split("").reverse().join("");
});
</script>
3.对计算属性进行修改
<template>
<span>{{ money }}</span> <br />
<span>{{ money2 }}</span> <br />
<button @click="setMoney">修改</button>
</template>
<script setup>
import { ref, computed } from "vue";
const money = ref(100);
let money2 = computed({
get() {
return money.value.toFixed(2);
},
set(value) {
money.value = value;
},
});
const setMoney = () => {
money2.value = 200;
};
</script>
(九) watch和watchEffect
- 用途: 改一个, 触发多个改变
- 监听多个
- 监听对象
1.用途
<template>
<span>{{ zhangsan }}</span> <br />
<span>{{ lisi }}</span> <br />
<button @click="setMoney(100)">修改</button>
</template>
<script setup>
import { ref, watch } from "vue";
const zhangsan = ref(0);
const lisi = ref(0);
const money = ref(0);
watch(money, (newVal, oldVal) => {
zhangsan.value = newVal * 0.6;
lisi.value = newVal * 0.4;
});
const setMoney = (num) => {
money.value = num;
};
</script>
2.监听多个属性
<template>
<span>{{ zhangsan }}</span> <br />
<span>{{ lisi }}</span> <br />
<button @click="setMoney(100)">修改</button>
</template>
<script setup>
import { ref, watch } from "vue";
const zhangsan = ref(0);
const lisi = ref(0);
const money = ref(0);
const money2 = ref(0);
watch([money, money2], (newVal, oldVal) => {
console.log(newVal);
console.log(oldVal);
});
const setMoney = (num) => {
money.value = num;
money2.value = num;
};
</script>
3.监听对象
因为对象是引用数据类型, 所以新旧值都一样,vue3 不会保留原对象的副本
- 监听整个对象
- 监听对象的某个属性
1.监听整个对象
<template>
<span>{{ user.name }}</span> <br />
<span>{{ user.age }}</span> <br />
</template>
<script setup>
import { reactive, watch } from "vue";
const user = reactive({
name: "张三",
age: 20,
});
watch(user, (newVal, oldVal) => {
console.log(newVal);
console.log(oldVal);
});
setTimeout(() => {
user.name = "李四";
user.age = 100;
}, 2000);
</script>
2. 监听对象属性
<template>
<span>{{ user.name }}</span> <br />
<span>{{ user.age }}</span> <br />
</template>
<script setup>
import { reactive, watch } from "vue";
const user = reactive({
name: "张三",
age: 20,
});
watch(
() => user.name,
(newVal, oldVal) => {
console.log(newVal);
console.log(oldVal);
}
);
setTimeout(() => {
user.name = "李四";
user.age = 100;
}, 2000);
</script>
(十) 模板和指令
- v-text v-html
- 属性绑定
- 事件绑定
(十一) 父子组件通信
- 父传子
- 子传父
1.父传子
// demo.vue
<template>
<Item title="hello web" />
<Item :title="title" />
</template>
<script setup>
import { ref } from "vue";
import Item from "./Item.vue";
let title = ref("我爱web");
</script>
// Item.vue
<template>
<h3>{{ props.title }}</h3>
</template>
<script setup>
let props = defineProps({
title: String,
});
</script>
2.子传父
// 父组件
<template>
<Item :title="title" @getData="changeFn" />
</template>
<script setup>
import { ref } from "vue";
import Item from "./Item.vue";
let title = ref("我爱web");
const changeFn = (str) => {
title.value = str;
};
</script>
// 子组件
<template>
<Item :title="title" @click="handleClick" />
</template>
<script setup>
import { ref } from "vue";
import Item from "./Item.vue";
let title = ref("我爱web");
const emits = defineEmits(['getData'])
const handleClick = () => {
emits('getData','从子组件传回消息');
};
</script>
(十二) vue3 路由
- 创建路由
- 挂载路由
- 路由跳转
1.创建路由
/router/index.js
import {createRouter,createWebHashHistory,createWebHistory} from 'vue-router';
const routes = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
component: ()=>import('../views/home.vue')
},
{
path: '/about',
component: ()=>import('../views/about.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router;
1.挂载路由
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
const app = createApp(App)
app.use(router)
app.mount('#app')
1.路由跳转
app.vue
<template>
<router-link to="/home">home</router-link>
<router-link style="margin-left: 15px;" to="/about">about</router-link>
<hr />
<router-view></router-view>
</template>
<script setup>
import { ref } from "vue";
import Item from "./Item.vue";
let title = ref("我爱web");
const changeFn = (str) => {
title.value = str;
};
</script>
(十三) Pinia
(1) Pinia 简介与基础
1.1 Pinia 简介
官方地址:https://pinia.vuejs.org/ Pinia 是 Vuex4 的升级版,也就是 Vuex5 Pinia 极大的简化了Vuex的使用,是 Vue3的新的状态管理工具 Pinia 对 ts的支持更好,性能更优, 体积更小,无 mutations,可用于 Vue2 和 Vue3 Pinia支持Vue Devtools、 模块热更新和服务端渲染
1.2 Pinia 基础
Vuex 与 Pinia 对比
Vuex 中核心部分: State、Getters、Mutations(同步) 和 Actions(异步) Pinia 中核心部分: State、Getters 和 Actions(同步异步均支持)
Pinia 各部分作用
State: 类似于组件中data,用于存储全局状态 Getters: 类似于组件中的computed,根据已有的State封装派生数据,也具有缓存的特性 Actions: 类似于组件中的methods,用于封装业务逻辑,同步异步均可以
Pinia 官方示例JS版本
import { defineStore } from "pinia";
export const todos = defineStore("todos", {
state: () => ({
/** @type {{ text: string, id: number, isFinished: boolean }[]} */
todos: [],
/** @type {'all' | 'finished' | 'unfinished'} */
filter: "all",
// type will be automatically inferred to number
nextId: 0,
}),
getters: {
finishedTodos(state) {
// autocompletion! ✨
return state.todos.filter((todo) => todo.isFinished);
},
unfinishedTodos(state) {
return state.todos.filter((todo) => !todo.isFinished);
},
// @returns {{ text: string, id: number, isFinished: boolean }[]}
filteredTodos(state) {
if (this.filter === "finished") {
// call other getters with autocompletion ✨
return this.finishedTodos;
} else if (this.filter === "unfinished") {
return this.unfinishedTodos;
}
return this.todos;
},
},
actions: {
// any amount of arguments, return a promise or not
addTodo(text) {
// you can directly mutate the stat 00e
this.todos.push({ text, id: this.nextId++, isFinished: false });
},
},
});
(2) Pinia 在Vue3-Vite中的使用
2.1 基础使用流程
① 创建一个vue vite项目
create-vite@latest
cd pinia-demo
npm install
npm run dev
② 安装 pinia
npm install pinia -S
package.json文件中
"dependencies": {
"pinia": "^2.0.9",
"vue": "^3.2.25"
},
③ 创建 pinia 实例并挂载到 vue中
// main.ts 文件
import { createApp } from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'
// 创建 Pinia 实例
const pinia = createPinia()
// 创建 Vue 实例
const app = createApp(App)
// 挂载到 Vue 根实例
app.use(pinia)
app.mount('#app')
④ 在src文件下创建一个store文件夹,并添加index.ts
// store/index.ts
import { defineStore } from 'pinia'
// 1. 定义容器、导出容器
// 参数1:容器的ID,必须是唯一的,后面Pinia会把所有的容器挂载到根容器
// 参数2:一些选项对象,也就是state、getter和action
// 返回值:一个函数,调用即可得到容器实例
export const useMainStore = defineStore('main',{
// 类似于Vue2组件中的data,用于存储全局状态数据,但有两个要求
// 1. 必须是函数,目的是为了在服务端渲染的时候避免交叉请求导致的数据状态污染
// 2. 必须是箭头函数,这样是为了更好的 TS 类型推导
state:()=>{
return {
info:"pinia 可以使用"
}
},
getters:{},
actions:{}
})
// 2. 使用容器中的 state
// 3. 通过 getter 修改 state
// 4. 使用容器中的 action 同步和异步请求
⑤ 在组件中使用
<template>
<h1>{{ mainStore.info}}</h1>
</template>
<script lang="ts" setup>
import { useMainStore } from "../store";
const mainStore = useMainStore();
</script>
<style>
</style>
2.2 state 中数据的解构访问
状态管理中
// store/index.ts
state:()=>{
return {
info:"pinia 可以使用",
count:10
}
},
组件中
<template>
<h1>{{ mainStore.count }}</h1>
<h1>{{ mainStore.info }}</h1>
<hr />
<h1>{{ count }}</h1>
<h1>{{ info }}</h1>
<p>
<button @click="alertData">修改数据</button>
</p>
</template>
<script lang="ts" setup>
import { toRefs } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore } from "../store";
const mainStore = useMainStore();
// 解构数据,但是得到的数据是不具有响应式的,只是一次性的
// 相当于仅仅只是...mainStore而已,只是做了reactive处理,并没有做toRefs
// const { count, info } = useMainStore();
// 解决方法:
// 1. 通过使用toRefs函数,因为前面所说相当于是通过reactive处理,因此可以
// const { count, info } = toRefs(mainStore);
// 2. 通过pinia中提供的storeToRefs方法来解决,推荐使用
const { count, info } = storeToRefs(mainStore);
const alertData = () => {
mainStore.count += 10
}
</script>
<style>
</style>
2.3 state 中数据的修改方式(actions和组件中)
一般的修改
const alertData = () => {
// 方式一:最简单的方法,如下
// 解构后更改方式
// count.value += 10
// 结构前更改方式
// mainStore.count += 10
// 方式二:若要同时修改多个数据,建议使用$patch来实现批量更新,在内部做了优化
// mainStore.$patch({
// count: mainStore.count + 1,
// info: "hello"
// })
// 方式三:更好的批量更新方法,通过$patch传递一个函数来实现,这里的state就是useMainStore容器中的state
mainStore.$patch(state => {
state.count += 10
state.info = "pinia批量更新"
})
}
通过actions修改
// store/index.ts
// 类似于vue2组件的methods,用于封装业务逻辑,修改state
// // 注意:不能使用箭头函数来定义actions,因为箭头函数绑定外部的this
actions:{
changeState (){
this.count += 10
this.info = "actions修改数据"
},
changeStates (num:number){
this.count += num + 2
this.info = "actions修改数据"
}
}
const alertData = () => {
// 方式一:最简单的方法,如下
// 解构后更改方式
// count.value += 10
// 结构前更改方式
// mainStore.count += 10
// 方式二:若要同时修改多个数据,建议使用$patch来实现批量更新,在内部做了优化
// mainStore.$patch({
// count: mainStore.count + 1,
// info: "hello"
// })
// 方式三:更好的批量更新方法,通过$patch传递一个函数来实现,这里的state就是useMainStore容器中的state
// mainStore.$patch(state => {
// state.count += 10
// state.info = "pinia批量更新"
// })
// 方式四:通过 actions 来修改数据
mainStore.changeState()
mainStore.changeStates(10)
}
2.4 getters 的使用 定义
// 类似于组件的computed,用来封装计算属性,具有缓存的功能
getters:{
// 函数接收一个可选参数:state状态对象
count10(state){
return state.count += 10
},
count10(state){
return this.count += 10
},
// 若使用this.count,则必须指明返回数据的类型
count11():number{
return this.count += 11
}
},
使用
<h1>{{ mainStore.count10 }}</h1>
(3) Pinia 数据持久化
3.1 保存至localStorage中
import { defineStore } from 'pinia';
const useLoginStore = defineStore({
id: 'login',
// state: () => ({
// num: 1,
// }),
state: () => ({
info: 'pinia 可以使用',
}),
getters: {},
actions: {
alertInfo() {
this.info = '可以可以,这个秒';
},
},
});
// 数据持久化
// 1. 保存数据
const instance = useLoginStore();
instance.$subscribe((_, state) => {
localStorage.setItem('login-store', JSON.stringify({ ...state }));
});
// 2. 获取保存的数据,先判断有无,无则用先前的
const old = localStorage.getItem('login-store');
if (old) {
instance.$state = JSON.parse(old);
}
export default useLoginStore;
使用 插件 pinia-plugin-persist 可以辅助实现数据持久化功能
3.2 安装插件
pnpm install pinia-plugin-persist --save
// main.ts文件中
import { createPinia } from 'pinia';
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import piniaPluginPersist from 'pinia-plugin-persist';
const pinia = createPinia();
pinia.use(piniaPluginPersist);
const app = createApp(App);
app.use(router);
app.use(pinia);
app.mount('#app');
// 接着在对应的 store 里开启 persist 即可。数据默认存在 sessionStorage 里,并且会以 store 的 id 作为 key。
import { defineStore } from 'pinia';
import piniaPluginPersist from 'pinia-plugin-persist';
const useLoginStore = defineStore({
id: 'login',
// state: () => ({
// num: 1,
// }),
state: () => ({
info: 'pinia 可以使用',
}),
// 开启数据缓存
persist: {
enabled: true,
},
getters: {},
actions: {
alertInfo() {
this.info = '可以可以,这个秒';
},
},
});
export default useLoginStore;
其它设置,自定义保存名称,保存位置和需要保存的数据
// 开启数据缓存
persist: {
enabled: true,
strategies: [
{
// 自定义名称
key: 'login_store',
// 保存位置,默认保存在sessionStorage
storage: localStorage,
// 指定要持久化的数据,默认所有 state 都会进行缓存,你可以通过 paths 指定要持久化的字段,其他的则不会进行持久化。
paths: ['age'],
},
],
},
(十四) vue3抽离业务逻辑
介绍:
在vue中,为了实现项目可维护性与复用的功能,会将代码单独分离出一个功能组件,但是vue2和vue3分离方式并不相同
vue2:
vue2不支持逻辑的抽离,如果想实现抽离必须要将所有html代码与逻辑代码全部抽离,会增加父子组件的通信成本,如果数据过多维护起来会比较头疼
vue3:
vue3支持逻辑的抽离,可以更好的增加项目的可维护性,由于抽离的只是逻辑代码 ,所以没有增加通信成本,就算数据再多,维护起来也非常方便
而且vue3中的 setup 这个函数里面不应该出现大量的逻辑代码,不仅看起来不优美,而且维护起来也有些不方便
在vue3中,一般会将抽离的逻辑代码放进service的文件夹中,然后在父组件中导入使用
(1) 未分离的写法
<ul class="goods-list">
// 直接使用即可
<li v-for="item in newData" :key="item.id">
<RouterLink to="/">
<img :src="item.picture" alt="" />
<p class="name">{{ item.title }}</p>
<p class="desc">{{ item.alt }}</p>
</RouterLink>
</li>
</ul>
......
// 导入 api 接口
<script>
import { findNew } from '@/api/home'
import { ref } from 'vue'
export default {
setup () {
// 获取的数据
const newData = ref([])
async function loadNewData () {
const res = await findNew()
newData.value = res.data.result
}
loadNewData()
// 不要忘记 return
return {
newData
}
</script>
(2) 抽离写法
功能组件:src/views/Home/service/useHot.js
// 功能组件
import { ref } from 'vue'
// 导入 api 接口
import { findHot } from '@/api/home'
export function useHot () {
const hotData = ref([])
async function loadhotData () {
const res = await findHot()
hotData.value = res.data.result
}
loadhotData()
// 注意要 return
return {
hotData
}
}
父组件:src/views/Home/index.vue
<ul class="goods-list">
// 直接使用即可
<li v-for="item in hotData" :key="item.id">
<RouterLink to="/">
<img :src="item.picture" alt="" />
<p class="name">{{ item.title }}</p>
<p class="desc">{{ item.alt }}</p>
</RouterLink>
</li>
</ul>
......
// 先导入
<script setup>
import { useHot } from './service/useHot'
export default {
setup () {
// 固定写法
const { hotData } = useHot()
// 记得 return
return {
hotData
}
}
</script>
(十五) vue3-admin模板
- 地址: https://fantastic-admin.netlify.app/
- 克隆项目
- 安装依赖
- 执行
npm i pnpm
- 执行
pnpm i
- 执行
npm run dev
- 执行
(十六) vue3添加@短路径
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve:{
alias:{
'@':path.resolve(__dirname,'./src')
}
}
})
(十七) Ts配置相关
(1) ts忽略检查
https://www.csdn.net/tags/OtDacg5sODM4NjMtYmxvZwO0O0OO0O0O.html
忽略一行的检查
// @ts-ignore
(2) ts不识别@别名路径
https://blog.csdn.net/xjtarzan/article/details/123660435
使用TS构建vue3项目时,如果使用例如 import { store } from ‘@/store/user’ 发生红色波浪线报错,说明ts不识别@别名,可以修改 tsconfig.json 文件,添加 baseUrl 和 paths 2个属性:
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
(十八) vite配置-跨域-别名-
注意: vite需要2.x版本
知识点
- 跨域配置
- 别名配置
- 全局css变量
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from 'path';
import { viteMockServe } from "vite-plugin-mock";
export default ({ mode, command }) => {
const prodMock = false;
return defineConfig({
resolve: {
// 别名配置
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
plugins: [
vue(),
// mock配置
viteMockServe({
mockPath: "./src/service", // 设置模拟.ts 文件的存储文件夹
localEnabled: command === "serve", // 设置是否启用本地 xxx.ts 文件,不要在生产环境中打开它.设置为 false 将禁用 mock 功能
prodEnabled: command !== "serve" && prodMock, // 设置打包是否启用 mock 功能
supportTs: true, // 打开后,可以读取 ts ⽂件模块。请注意,打开后将⽆法监视.js ⽂件。
watchFiles: true, // 监视⽂件更改,并重新加载 mock 数据
}),
],
// 跨域和端口配置
server: {
port: 8888,
proxy: {
"/api": {
target: "http://81.71.65.4:5004", // 后端服务实际地址
changeOrigin: true, //开启代理
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
});
};
mockServer配置: https://blog.csdn.net/sinat_35082096/article/details/124470571
(十九) 图片懒加载
(1) 使用插件
- 安装插件
npm install vue3-lazy -S
- 初始化插件
import App from './App.vue'
import lazyPlugin from 'vue3-lazy'
createApp(App)
.use(lazyPlugin, {
loading: require('@/assets/images/default.png'), // 图片加载时默认图片
error: require('@/assets/images/error.png')// 图片加载失败时默认图片
})
.mount('#app')
- 使用
<ul>
<li v-for="img in list">
<img v-lazy="img.src" >
</li>
</ul>
(2) 自定义指令
// 图片加载不出来时显示的图片
import defaultImg from '@/assets/images/200.png'
const defineDirective = (app) => {
// 扩展自定义指令
app.directive('lazyload', {
// Vue2规则 :vue.directive('lazyload',{
// inserted () {}
})
// Vue3规则:mounted
mounted(el, bindings) {
// el表示使用指令的DOM元素
// bindings表示指令相关的信息是一个对象
// 指令的功能:实现图片的懒加载
// 1、监听图片进入可视区
const oberver = new IntersectionObserver(([{ isIntersecting }]) => {
if (isIntersecting) {
// 进入了可视区
// 2、给图片的src属性赋值图片的地址
el.src = bindings.value
// 取消图片的监听
oberver.unobserve(el)
// 加载的图片失败了,显示默认的图片地址
el.onerror = () => {
// 显示默认图片
el.src = defaultImg
}
}
})
oberver.observe(el)
}
})
}
export default {
install(app) {
// 自定义指令
defineDirective(app)
}
}
<template>
<div class="goods-item">
<RouterLink to="/" class="image">
// v-lazyload取到了后端数据图片地址传给到
// IntersectionObserver函数里
<img v-lazyload="goods.picture" alt="">
</RouterLink>
<p class="name ellipsis-2">{{goods.name}}</p>
<p class="desc ellipsis">{{goods.desc}}</p>
<p class="price">¥{{goods.price}}</p>
<div class="extra">
<RouterLink to="/">
<span>找相似</span>
<span>发现现多宝贝 ></span>
</RouterLink>
</div>
</div>
</template>
<script>
export default {
name: 'HomeGoods',
props: {
goods: {
type: Object,
default: () => {}
}
}
}
</script>
https://blog.csdn.net/cmhahaha/article/details/120336211
(十九) defineProps如何添加类型校验
- 3.2以上的版本不需要导入defineProps也可以直接使用了
(二十) 后台管理系统自动生成模块代码
(二十一) 修正vetur检查
(1) 方式1 好像没有用
为了解决vscode的vetur插件 把vue3项目当成vue2去检查 然后出现了报错非要template下仍然有要标签的报错
第一步:打开你的package.json
第二步:添加以下代码
"eslintConfig": {
"rules": {
"vue/no-multiple-template-root": "off"
}
}
第三步: 重启vsCode
(2) 方式2
解决使用vscode对vue3的template检测报错问题
- 打开首选项 -> 设置 -> 搜索vetur valid
- 把template, script的检验都取消
(二十二) props解构
(二十三) defineExpose暴露子组件的变量和方法
(1) 父组件
<template>
<Son ref="sonRef"></Son>
<span @click="handleClick">显示</span>
</template>
<script setup lang='ts'>
import Son from './Son.vue';
import { ref } from 'vue';
const show = ref(false);
const sonRef = ref()
const handleClick = () => {
//获取ref中的子组件方法handleNodeClick()
sonRef.value.handleNodeClick();
sonRef.value.show = !sonRef.value.show;
}
</script>
(2) 子组件
<template>
<div v-show="show">22222</div>
</template>
<script setup lang='ts'>
import { ref } from 'vue'
const show = ref(true);
const handleNodeClick = () => {
console.log('要执行的方法')
}
//将变量和方法暴露出
defineExpose({show, handleNodeClick })
</script>
(二十四) reative对象或数组重置
(二十五) defineProps、defineEmits、defineExpose
vue3.2中的defineProps、defineEmits、defineExpose
https://blog.csdn.net/weixin_43550562/article/details/124705539
(1) defineProps
注意: defineProps解构后无法被watch到
<template>
<h1>{{ msg }}</h1>
</template>
<script setuplang="ts">
// 采⽤ts专有声明,⽆默认值
defineProps<{
msg: string,
num?: number
}>()
// 采⽤ts专有声明,有默认值
interface Props {
msg?: string
labels?: string[]
}
const props =withDefaults(defineProps<Props>(),{
msg:'hello',
labels:()=>['one','two']
})
// ⾮ts专有声明
defineProps({
msg: String,
num:{
type:Number,
default:''
}
})
</script>
Vue3 理解 toRef 和 toRefs 的作用、用法、区别
https://blog.csdn.net/cookcyq__/article/details/121618833
父子组件通信-单向数据流
需求: 后台管理系统, 在列表中点击编辑, 弹出表单, 修改表单(但希望修改表单的时候表格的数据不受影响), 当点击表单确认的时候发请求去后台进行修改, 修改成功后再去更新表格中的内容
(1) 父组件
<template>
<div>
<p>{{ form.username }} {{ form.age }}</p>
<hr style="margin-top: 30px;">
<SonVue :form="form" @submit="onSubmit"/>
</div>
</template>
<script setup lang='ts'>
import SonVue from './Son.vue';
import { ref } from "vue";
const form = ref({
username: 'zhangsan',
age: 18
})
const onSubmit = (data)=> {
form.value = data;
}
</script>
(2) 子组件
<template>
<div>
<el-input style="width:200px;" v-model="formData.username"></el-input>
<el-input v-model="formData.age"></el-input>
<el-button @click="handleClick">提交</el-button>
</div>
</template>
<script setup lang='ts'>
import { ref, watchEffect } from "vue";
const {form} = defineProps({
form: Object
})
const emits = defineEmits(['submit'])
const handleClick = () => {
emits('submit',formData.value);
}
const clone = (obj: Object) => {
try {
let str = JSON.stringify(obj);
return JSON.parse(str);
} catch (error) {
return obj;
}
};
let formData = ref({});
watchEffect(()=> {
formData.value = clone(form);
})
</script>
(二十六) vue+ts defineProps自定义类型
<template>
<div>
<p>
<input type="text" :value="form.name">
</p>
</div>
</template>
<script setup lang='ts'>
import { AAA } from './AAA';
const {form} = defineProps<{
form: AAA;
}>();
(二十七) vue3+ts 子组件是表单的父子通信问题
(二十八) ts使用@报错处理
(二十九) vue+ts声明响应式变量指定类型
https://www.jianshu.com/p/08887a101755
const a = ref('') //根据输入参数推导字符串类型 Ref<string>
const b = ref<string[]>([]) //可以通过范型显示约束 Ref<string[]>
const c: Ref<string[]> = ref([]) //声明类型 Ref<string[]>
const list = ref([1, 3, 5])
console.log('list前:', list.value)
list.value[1] = 7
console.log('list后:', list.value)
type typPeople = {
name: string
age: number
}
const list2: Ref<typPeople[]> = ref([])
console.log('list2-前:', list2.value) //{} 不是空数组,而是空对象
list2.value.push({ name: '小张', age: 18 })
console.log('list2-后:', list2.value[0]) //{name: '小张', age: 18}
********* ref 内部值指定类型 *********
const foo = ref<string | number>('foo')
foo.value = 123
********* 如果ref类型未知,则建议将 ref 转换为 Ref<T>: *********
function useState<T>(initial: T) {
const state = ref(initial) as Ref<T>
return state
}
const item: typPeople = { name: 'aa', age: 18 }
const x1 = useState(item) // x1 类型为: Ref<typPeople>
const x2 = ref(item) //x2 类型为: Ref<{ name:string; age: number;}>
console.log('ref.value:', x1.value, x1.value.name)
//Proxy{name: 'aa', age: 18} aa
(三十) vue2双向数据绑定
/ 父组件
<template>
<div> 数量: {{num}}</div>
<!-- <ChildComponent :num="num" @increase="num = $event"/> -->
<ChildComponent :num.sync="num" />
</template>
//子组件
<template>
<div @click="addNum"> 接收数量: {{num}}</div>
</template>
<script>
export default {
props: ['num'],
// data() {
// return {
// childNum: this.num
// }
// },
methods: {
addNum() {
// this. childNum++
// this.$emit('increase', this. childNum)
this.$emit('update:num', this.num + 1)
}
}
}
(三十一) vue3双向数据绑定
(三十二) vue3获取子组件实例
// 父组件
<el-button @click="handleAddAttr">新增</el-button>
<AddAttrVue ref="attrRef"/>
<script setup>
const handleAddAttr = () => {
attrRef.value.show = true;
}
</script>
// 子组件
<template>
......
</template>
<script setup>
const show = ref(false);
defineExpose({
show,
})
</script>
(三十三) vue3+ts router配置
(1) 常规配置
createRouter
创建router
实例router
的模式分为:createWebHistory
-- history模式createWebHashHistory
-- hash模式routes
的约束类型是RouteRecordRaw
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import Home from '../views/Home.vue';
const routes: Array< RouteRecordRaw > = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
});
export default router;
(2) 扩展路由额外属性
// 联合类型
type RouteConfig = RouteRecordRaw & {hidden?: boolean}; //hidden 是可选属性
const routes: Array<RouteConfig> = [
{
path: '/',
name: 'Home',
component: Home,
hidden: true,
meta: {
permission: true,
icon: ''
}
}
];
(三十四) pinia及持久化
http://www.manongjc.com/detail/29-hcjeycqtuguajxb.html
(1) src/store/index.ts
import { createPinia } from "pinia";
import piniaPluginPersist from 'pinia-plugin-persist'
const store = createPinia();
// 持久化
store.use(piniaPluginPersist);
export default store;
(2) src/main.ts
import { createApp } from 'vue'
import store from './store'
const app = createApp(App)
app.use(store)
app.mount('#app')
(3) src/store/user.ts
import { defineStore } from "pinia";
export const useUserStore = defineStore({
id:'user',
state() {
return {
name: '',
age: null,
}
},
// 持久化
persist: {
enabled: true,
strategies: [
{
key: 'user',
storage: localStorage,
}
]
}
})
(三十五) vue3使用全局变量
app.config.globalProperties.$isDev = process.env.NODE_ENV;
import { getCurrentInstance } from "vue";
console.log();
(三十六) 动态组件
参考链接:
https://www.jianshu.com/p/392b03806968
https://blog.csdn.net/qq1195566313/article/details/122891279
(1) vue2动态组件
动态组件是不固定要显示哪个组件,只是有个component标签,表示在模板的相应位置有一个组件, 它具体显示哪个组件,要根据它的is属性来决定, 动态组件多见于tab栏, 根据点击的tab来显示其对应的组件
// 父组件
<template>
<div>
<button @click="showCom('A')">显示A</button>
<button @click="showCom('B')">显示B</button>
<hr />
<component :is="currComponent" />
</div>
</template>
<script>
import ACom from "./A.vue";
import BCom from "./B.vue";
export default {
components: {
ACom,
BCom,
},
data() {
return {
currComponent: "",
};
},
methods: {
showCom(name) {
if (name === 'A') {
this.currComponent = ACom;
} else {
this.currComponent = BCom;
}
},
},
};
</script>
// 子组件A.vue
<template>
<div>A组件</div>
</template>
// 子组件B.vue
<template>
<div>B组件</div>
</template>
(2) vue3动态组件
// 一般做法
<template>
<div>
<button @click="showCom('A')">显示A</button>
<button @click="showCom('B')">显示B</button>
<hr />
<component :is="currComponent" />
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import ACom from "./A.vue";
import BCom from "./B.vue";
const currComponent = ref(null);
const showCom = (name: any) => {
if (name === "A") {
currComponent.value = ACom;
} else {
currComponent.value = BCom;
}
};
</script>
注意事项
以上做法能显示效果, 但会弹出一下警告
在Vue2 的时候is 是通过组件名称切换的 在Vue3 setup 是通过组件实例切换的
如果你把组件实例放到Reactive Vue会给你一个警告runtime-core.esm-bundler.js:38 [Vue warn]: Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with
markRaw
or usingshallowRef
instead ofref
. Component that was made reactive:
这是因为reactive 会进行proxy 代理 而我们组件代理之后毫无用处 节省性能开销 推荐我们使用shallowRef 或者 markRaw 跳过proxy 代理
- 去处警告只需要用shallowRef替代ref即可
(三十七) Vue3中shallowReactive 与 shallowRef 的用法
参考链接: https://blog.csdn.net/qq_54527592/article/details/119840044
(1) reactive 和 showReactive
<template>
<div>
<h1>姓名:{{ person.name }}</h1>
<h2>年龄:{{ person.age }}</h2>
<h3>喜欢的水果:{{ person.likeFood.fruits.apple }}</h3>
<button @click="person.name += '~'">修改姓名</button>
<button @click="person.age++">修改年龄</button>
<button @click="person.likeFood.fruits.apple += '!'">修改水果</button>
</div>
</template>
<script setup lang="ts">
import { reactive, shallowReactive } from "vue";
let person = shallowReactive({
// 只将第一层数据做了响应式处理
name: "张三",
age: 18,
likeFood: {
fruits: {
apple: "苹果", // 深层次的数据将会是一个普通的对象, 不具备响应性
},
},
});
</script>
(2) ref 和 showRef
参考链接: https://blog.csdn.net/LiuMH2011/article/details/123716933
ref很容易理解,使用ref创建的对象,里面任意深度的属性与视图都是响应性的
const data = ref({ a: { b: 1, }, })
当修改b属性的值时,视图会更新
shallowRef就是浅的意思, 与ref不同,shallowRef修改深层属性时,并不会更新视图,如:
<template> <div> <p>{{ data.foo }}</p> <button @click="update">update</button> <button @click="log">log</button> </div> </template> <script setup> import {shallowRef} from 'vue'; const data = shallowRef({ foo: "1", }); function update() { data.value.foo = "2"; } function log() { console.log(data.value.foo); } </script>
上例点击update时,视图并不会更新,但是点击log按钮时,打印出foo的值为2.
想要更新视图,必须给value赋值,直接替换整个对象。修改update方法:
function update() { data.value = { foo: 200 } }
再次点击update按钮,视图更新。
shalow即浅的意思,shallowRef只有整个数据变更时才刷新视图。
或者在修改了数据之后,调用
triggerRef方法,主动触发视图刷新:
function update() { data.value.foo = '300'; triggerRef(data); // 触发视图刷新 }
为什么要使用shallowRef
因为ref方法会递归遍历对象的所有属性,使所有属性都具备响应性,所以,当对象很复杂且庞大时,过多的监听会导致性能上的损耗。如假设有一个文章列表数组:
list = [ { title: '', auto: '', time: '' }, { title: '', auto: '', time: '' }, { title: '', auto: '', time: '' }, //... ]
(三十八) 动态样式
vue3支持样式中使用响应式变量
<template>
<button @click="count+=2">字体大小: {{count}}px</button>
</template>
<script setup lang='ts'>
import {ref} from 'vue';
const count = ref(10);
</script>
<style scoped>
button {
font-size: v-bind(count+'px');
}
</style>
(三十九) ts使用node模块
安装node的声明文件
npm i @types/node --save-dev
import path from 'path'
报错的处理给根目录下的
tsconfig.node.json
添加"allowSyntheticDefaultImports": true,
(四十) 一个更快的包管理工具pnpm
安装
npm i pnpm -g
使用
pnpm i xxx
(四十一) useRoute和useRouter注意事项
两者只能写在setup中, 如果要在axios中使用, 需要导入router实例
import router from '@/router/index';
监测当前路由同理
watch(() => router.currentRoute.value.query, (query) => { // todo })
https://blog.csdn.net/weixin_47339511/article/details/117607327
(四十一) 如何给reactive变量赋值
const state = reactive({
arr: []
});
state.arr = [1, 2, 3]
const state = ref([])
state.value = [1, 2, 3]
const arr = reactive([])
arr.push(...[1, 2, 3])